Data Description

GPS tracking data for elk (N=31) from Hebblewhite et al 2008 (https://doi.org/10.1890/06-1708.1).

Data Summaries

Load Data In, Check Structure

load("./data/elk_gps.rda")
str(elk_gps)
## 'data.frame':    138433 obs. of  6 variables:
##  $ timestamp                  : chr  "3/25/2003 19:01:00" "3/25/2003 23:01:00" "3/26/2003 1:01:00" "3/26/2003 5:00:00" ...
##  $ location.long              : num  -115 -115 -115 -115 -115 ...
##  $ location.lat               : num  51.7 51.7 51.7 51.7 51.7 ...
##  $ migration.stage            : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ sensor.type                : chr  "gps" "gps" "gps" "gps" ...
##  $ individual.local.identifier: chr  "GP1" "GP1" "GP1" "GP1" ...

Make DateTime Posixct Format

elk_gps$timestamp <- as.POSIXct(elk_gps$timestamp, format="%m/%d/%Y %H:%M:%S")

Check For Missing Data

elk_gps2 <- subset(elk_gps, is.na(timestamp)==FALSE & is.na(location.long)==FALSE & is.na(location.lat)==FALSE)

Create Data Summary Table

library(plyr)
library(dplyr)
elk_gps2 |> group_by(individual.local.identifier) |> 
  summarize(Min_Time = min(timestamp),
            Max_Time = max(timestamp),
            Duration = round(difftime(max(timestamp), min(timestamp), units = "days")))
## # A tibble: 31 × 4
##    individual.local.identifier Min_Time            Max_Time            Duration
##    <chr>                       <dttm>              <dttm>              <drtn>  
##  1 4049                        2001-12-13 07:01:00 2002-11-14 03:00:00 336 days
##  2 GP1                         2003-03-25 19:01:00 2003-03-30 01:00:00   4 days
##  3 GP2                         2003-03-15 01:00:00 2004-01-04 09:01:00 295 days
##  4 GR104                       2004-03-29 19:00:00 2005-01-22 15:00:00 299 days
##  5 GR182                       2002-04-05 07:01:00 2002-12-17 17:00:00 256 days
##  6 GR193                       2002-04-05 23:01:00 2002-11-23 15:01:00 232 days
##  7 GR196                       2002-04-05 23:00:00 2002-06-15 08:30:00  70 days
##  8 YL15                        2003-02-16 13:00:00 2004-01-06 05:01:00 324 days
##  9 YL2                         2003-02-14 05:00:00 2004-01-14 19:02:00 335 days
## 10 YL25                        2003-03-03 22:06:00 2004-03-26 11:00:00 389 days
## # ℹ 21 more rows
elk_gps2 |> group_by(individual.local.identifier) |>
  summarize(time_range = as.numeric(difftime(max(timestamp), min(timestamp), units ="days"))) |> 
  summary()
##  individual.local.identifier   time_range     
##  Length:31                   Min.   :  4.249  
##  Class :character            1st Qu.:170.636  
##  Mode  :character            Median :269.000  
##                              Mean   :231.225  
##                              3rd Qu.:308.727  
##                              Max.   :388.538

Visualize Tracking Durations

library(ggplot2)
ggplot(data=elk_gps2,aes(x=timestamp,y=individual.local.identifier,color=individual.local.identifier))+ 
  geom_path(linewidth=1) +
  theme_classic() + 
  xlab("Time") + ylab("ID")+ 
  theme(legend.position = "none")

Drop Individuals With Short Tracks

elk_gps3 <- subset(elk_gps2, !individual.local.identifier %in% c("GP1", "YL72", "YL79"))
elk_gps3 |> group_by(individual.local.identifier) |>
  summarize(time_range = as.numeric(difftime(max(timestamp), min(timestamp), units ="days"))) |> 
  summary()
##  individual.local.identifier   time_range    
##  Length:28                   Min.   : 70.35  
##  Class :character            1st Qu.:212.72  
##  Mode  :character            Median :275.56  
##                              Mean   :255.21  
##                              3rd Qu.:318.69  
##                              Max.   :388.54
ggplot(data=elk_gps3,aes(x=timestamp,y=individual.local.identifier,color=individual.local.identifier))+ 
  geom_path(linewidth=1) +
  theme_classic() + 
  xlab("Time") + ylab("ID")+ 
  theme(legend.position = "none")

Examine the Fix Rate

dtime <- function(t, ...) {difftime(t[-1], t[-length(t)], ...) %>% as.numeric}

fix_rate <- elk_gps3 |> 
  group_by(individual.local.identifier) |>
  arrange(timestamp) |>
  mutate(dtime = c(0,round(dtime(timestamp, units ="hours")))) |>
  data.frame() |>
  ungroup()
barplot(table(fix_rate$dtime))

summary(fix_rate$dtime)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##    0.000    0.000    1.000    1.152    2.000 1135.000

Change Column Names

colnames(elk_gps3) <- c("datetime", "lon", "lat", "mig.stage", "sensor.type", "id")

Visualize Tracks

ggplot(data=elk_gps3, aes(x=lon, y=lat)) +
  geom_path(size=0.5, color="darkgrey") +
  theme_classic() +
  facet_wrap(~id, scale="free", ncol=3)

ggplot(data=elk_gps3, aes(x=datetime, y=lat)) +
  geom_path(size=0.5, color="darkgrey") +
  theme_classic() +
  facet_wrap(~id, scale="free", ncol=3)

Make Data Spatial

library(sf)
elk_sf <- elk_gps3 |>
  st_as_sf(coords = c("lon","lat"), crs=4326)
st_geometry(elk_sf)
## Geometry set for 138105 features 
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -116.4028 ymin: 51.38134 xmax: -115.3461 ymax: 52.1541
## Geodetic CRS:  WGS 84
## First 5 geometries:

Interactive Maps of Tracks

library(mapview)
elk_tracks <- elk_sf |> 
  group_by(id) |> 
  summarize(do_union=FALSE) |> 
  st_cast("LINESTRING")
mapview(elk_tracks, zcol="id")

Convert to Projected Coordinate System

Data is in UTM Zone 11 North (EPSG Code: 32611)

elk_utm <- elk_sf |>
  st_transform(32611)

Extract Coordinates and Add to Dataframe

latlon_coords <- st_coordinates(elk_sf)

xy_coords <- st_coordinates(elk_utm)
elk_df <- elk_utm |> data.frame() |>
  select(-geometry)
elk_df$lon <- latlon_coords[,1]
elk_df$lat <- latlon_coords[,2]

elk_df$X <- xy_coords[,1]
elk_df$Y <- xy_coords[,2]

Remove Duplicate Timestamps & Sort by Time

elk_df2 <- elk_df |>
  group_by(id) |>
  filter(!duplicated(datetime)) |>
  arrange(datetime) |>
  ungroup() |>
  data.frame()

Convert Locations into Complex Numbers

elk_df2$Z <- complex(re = elk_df2$X, im = elk_df2$Y)

# or

elk_df2$Z <- elk_df2$X + 1i*elk_df2$Y

Generate Movement Metrics

getMovementMetrics <- function(dataframe){
  
  move_step <- diff(dataframe$Z)  

  time_step <- as.numeric(difftime(dataframe$datetime[-1], dataframe$datetime[-length(dataframe$datetime)], "days"))

  absolute_turnangle <- Arg(move_step)

  relative_turnangle <- diff(absolute_turnangle)

  step_length_km <- Mod(move_step)/1000
  
  dataframe$time_step_days <- c(NA, time_step)

  dataframe$move_rate_km_day <- c(NA, step_length_km/time_step)

  dataframe$step_length_km <- c(NA, step_length_km)

  dataframe$relative_turnangle <- c(NA, NA, relative_turnangle)
  
  return(dataframe)
}
elk_df3 <- elk_df2 |> 
  ddply("id", getMovementMetrics)

Visualize and Summarize Movement Metrics

summary(elk_df3$step_length_km)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
##  0.00000  0.02128  0.07744  0.23433  0.25010 53.94923       28
elk_df3 |>
  group_by(id) |>
  filter(is.na(move_rate_km_day)==FALSE) |>
  summarize(Min_MoveRate = min(move_rate_km_day),
            Max_MoveRate = max(move_rate_km_day),
            Avg_MoveRate = mean(move_rate_km_day))
## # A tibble: 28 × 4
##    id    Min_MoveRate Max_MoveRate Avg_MoveRate
##    <chr>        <dbl>        <dbl>        <dbl>
##  1 4049    0                3.82        0.147  
##  2 GP2     0                0.0619      0.00317
##  3 GR104   0                0.386       0.00405
##  4 GR182   0                0.128       0.00439
##  5 GR193   0.000173         1.77        0.145  
##  6 GR196   0.00000575       0.0504      0.00328
##  7 YL15    0                0.140       0.00290
##  8 YL2     0                0.117       0.00342
##  9 YL25    0                0.142       0.00362
## 10 YL29    0                0.0797      0.00399
## # ℹ 18 more rows
steplengths <- na.omit(elk_df3$step_length_km)

hist(steplengths, col="grey", bor="black", freq=FALSE, breaks=50)
lines(density(steplengths), col=2, lwd=2)

library(circular)
turningangles <- as.circular(na.omit(elk_df3$relative_turnangle))

rose.diag(turningangles, bins=20, col="grey", prop=2)

Save Your Processed Data

elk_processed <- elk_df3

save(elk_processed, file="./data/elk_processed.rda")